package org.dreamwork.jasmine2.parser;

import org.dreamwork.text.TextParser;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Stack;
import java.util.List;

public class DocumentReader extends TextParser {
    boolean intag = false, recordtag = false, recordattr = false;
    int quot = -1;
    String currentTag;
    Tag current;
    Stack<Tag> stack = new Stack<Tag> ();
    ArrayList taglib = new ArrayList ();
    Hashtable<String, List<Tag>> namespace = new Hashtable<String, List<Tag>> ();

    public ArrayList getTaglib () {
        return taglib;
    }

    public ArrayList getTagsByNamespace (String namespace) {
        return (ArrayList) this.namespace.get (namespace);
    }

    public DocumentReader (File file) throws IOException {
        super (file);
    }

    public DocumentReader (URL url) throws IOException {
        super (url);
    }

    public DocumentReader (InputStream in) throws IOException {
        super (in);
    }

    public DocumentReader (String src) {
        super (src);
    }

    public DocumentReader (Reader reader) throws IOException {
        super (reader);
    }

    public Tag parse () throws ParseException, IOException {
        Tag root = Tag.createRoot ();
        current = root;
        stack.push (root);
        Segment segment = new Segment (skipSpace (), 0);
        for (int ch = nextChar (); ch != -1; ch = nextChar ()) {
            switch (ch) {
                case '<':
                    if (!intag) { // 不在tag或脚本中，设置tag标志


                            int yy = cursor - segment.start - 1;
                            if (yy > 0) {
                                segment.length = yy;
                                process_content (segment);
                                segment.reset ();
                            } /*else {
                                segment.reset ();
                            }*/





                        if ((stream[cursor] == '!' || stream[cursor] == '%') &&
                                stream[cursor + 1] == '-' &&
                                stream[cursor + 2] == '-') { // 注释开始
                            int peek = stream[cursor];
                            skip (3);
                            segment = peek == '!' ? skipUntil ("-->") : skipUntil ("--%>");
                            if (segment == null) throw new ParseException ();
                            process_comment (segment);
                        } else if (peek () == '!') { // <! 文档定义
                            if ("![CDATA[".equals (new String (stream, cursor, 8))) {
                                Segment s = skipUntil ("]]>");
                                s.start ++;
                                s.length ++;
//                                cursor += 2;
                                current.addChild (new CDATANode (s.toString ().trim ()));
                                segment.reset ();
                            } else {
                                // 暂时不处理， 直接跳过
                                Segment s = skipUntil (">");
                                s.start ++;
                                s.length --;
                                current.addChild (new DocDeclartion (s.toString ()));
                                segment.reset ();
                            }
                        } else if (peek () == '%') {
                            nextChar ();
                            segment = skipUntil ("%>");
                            process_jsp (segment);
                            skipSpace ();
                            segment.reset ();
                        } else if (peek () == '?') {
                            // <?xml ... ?> xml 文档定义，跳过
                            skipUntil ("?>");
                            segment.reset ();
                        } else {
                            intag = true;
                            recordtag = true;
                            quot = -1;
/*
                            if (currentTag != null) {
                                segment.length = cursor - segment.start - 1;
                                process_content (segment);
                            } else {
                                segment.reset ();
                            }
*/
                            int length = cursor - segment.start - 1;
                            if (length > 0) {
                                segment.length = length;
                                process_content (segment);
                            } else {
                                segment.reset ();
                            }
                        }
                    } else {
                        if (stream[cursor] == '%' && stream[cursor + 1] == '#') { // <%# 内部
                            skipUntil ("%>");
                        }
                    }
                    break;
                case '>':
                    if (intag && quot < 0) {
                        if (recordattr) { // 有属性的标签
                            segment.length = cursor - segment.start - 1;
                            process_attributes (segment);
                        } else { // 无属性的标签
                            recordtag = false;
                            if (stream[segment.start] == '<') segment.start++;
                            segment.length = cursor - segment.start - 1;
                            process_tag (segment);
                        }
                        recordattr = false;
                        intag = false;
                        skipSpace ();
                        segment.reset ();
                        if ("script".equalsIgnoreCase (currentTag)) {
                            process_script ();
                            skipSpace ();
                            segment.reset ();
                        }

                        if (current.isEmptyTag ())
                            current = stack.pop ();
                    }
                    break;
                case ' ':
                    if (intag) { // 在tag中
                        if (recordtag) { // 正在记录tag名称
                            recordtag = false;
                            recordattr = true;
                            if (stream[segment.start] == '<') segment.start++;
                            segment.length = cursor - segment.start - 1;
                            process_tag (segment);
                        }
                    }
                    break;
                case '/':
                    if (intag) {
                        if (recordattr && quot < 0) {
                            segment.length = cursor - segment.start - 1;
                            process_attributes (segment);
                            if (!TagUtil.isEmptyTag (currentTag))
                                current = stack.pop ();
                        } else {
//                            if (stream [cursor + 1] == '>')  current = (Tag) stack.pop ();
                        }
                    }
                    break;
                case '\'':
                    if (intag) {
                        if (quot == '\'') {
                            if (stream[cursor - 1] != '\\') quot = -1;
                        } else if (quot < 0) {
                            quot = '\'';
                        }
                    }
                    break;
                case '"':
                    if (intag) {
                        if (quot == '"') {
                            if (stream[cursor - 2] != '\\') quot = -1;
                        } else if (quot < 0) {
                            quot = '"';
                        }
                    }
                    break;
                case '@':
                    break;
            }
        }

        return root;
    }

    private void process_jsp (Segment seg) throws ParseException {
        JspDirective dir = JspDirective.getDirective (seg.toString ());
        if (dir != null) {
            dir.parse ();
            current.addChild (dir);
        } else {
            String s = seg.toString ();
            if (s != null) {
                s = s.trim ();
                char c = s.charAt (0);
                if (c == '#') { // data binding expression
                    current.addChild (new BindingExpression (s.substring (1)));
                } else if (c == '=') {
                    current.addChild (new SimpleExpression (s.substring (1)));
                } else { // init script or class/method difinition
                    Tag tag = new Tag ("script");
                    tag.setAttribute ("language", "java");
                    tag.setAttribute ("runat", "server");
                    tag.addChild (new TextNode (s));
                    current.addChild (tag);
                }
            }
        }
    }

    private void process_script () {
        Segment seg = skipUntilOutQuot ("</script>", true);
        if (seg.length >= 0) {
            current.addChild (new TextNode (seg.toString ()));
            current = stack.pop ();
            currentTag = current.name;
        }
    }

    private void process_tag (Segment seg) {
        if (seg.length > 0) {
            String tagName = seg.toString ();
            if (tagName.startsWith ("/")) { // 关闭标签
/*
                if (current.name.equalsIgnoreCase ("li")) { // HTML ITEM元素的关闭标签是可选的
                    if (tagName.equalsIgnoreCase ("/ul") || tagName.equalsIgnoreCase ("/ol")) {
                        current = stack.pop ();
                    }
                }

                if (current.name.equalsIgnoreCase ("option") && tagName.equalsIgnoreCase ("/select")) {
                    current = stack.pop ();
                }
*/
                current = stack.pop ();
            } else {
                if (tagName.endsWith ("/")) {
                    current.addChild (new Tag (tagName.substring (0, tagName.length () - 1)));
                } else {
                    if (current.name.equalsIgnoreCase ("p")) {
                        if (TagUtil.isBlockTag (tagName)/* || tagName.equalsIgnoreCase ("li")*/) {
                            current = stack.pop ();
                        }

                        if (tagName.equalsIgnoreCase ("option")) {
                            current = stack.pop ();
                        }
                    }
/*
                    if (tagName.equalsIgnoreCase ("li")) {
                        if (current.name.equalsIgnoreCase ("li")) current = stack.pop ();
                        else if (!(current.name.equalsIgnoreCase ("ul") || current.name.equalsIgnoreCase ("ol"))) {
                            Tag t = current.parent;
                            int n = 0;
                            boolean found = false;
                            while (t != null) {
                                if (t.name.equalsIgnoreCase ("li")) {
                                    current = t.parent;
                                    found = true;
                                    break;
                                }

                                t = t.parent;
                                n++;
                            }

                            if (found) for (int i = 0; i <= n; i++) current = stack.pop ();
                        } else if (current.name.equalsIgnoreCase ("p")) {
                            current = stack.pop ();
                            if (current.name.equalsIgnoreCase ("li")) current = stack.pop ();
                        }
                    }
*/
                    if (tagName.equalsIgnoreCase ("option")) {
                        if (current.name.equalsIgnoreCase ("option")) current = stack.pop ();
                    }

                    Tag tag = new Tag (tagName);
                    current.addChild (tag);
                    stack.push (current);
                    current = tag;

                    if (tag.prefix != null) {
                        List<Tag> list = namespace.get (tag.prefix);
                        if (list == null) {
                            list = new ArrayList<Tag> ();
                            namespace.put (tag.prefix, list);
                        }
                        list.add (tag);
                    }
                }
                currentTag = tagName;
                seg.reset ();
            }

        }
    }

    private void process_content (Segment seg) {
        if (seg.length > 0) {
            current.addChild (new TextNode (seg.toString ()));
            seg.reset ();
        }
    }

    private void process_attributes (Segment seg) throws IOException {
        if (seg.length > 0) {
            CharArrayWriter caw = new CharArrayWriter ();
            int quot_char = -1;
            String attrName = null, attrValue = null;
            for (int i = 0; i < seg.length; i++) {
                int ch = stream[seg.start + i];
                switch (ch) {
                    case ' ':
                        if (quot_char > 0) caw.write (ch);
                        break;
                    case '=': // 如果=不出现在引号中，表示着属性值开始
                        if (quot_char > 0) { // 在引号中
                            caw.write (ch);
                        } else {
                            attrName = caw.toString ();
                            caw.reset ();
                        }
                        break;
                    case '\'':
                    case '"':
                        if (quot_char < 0) {
                            quot_char = ch;
                        } else {
                            if (quot_char == ch && stream [seg.start + i - 1] != '\\') {
                                quot_char = -1;
                                attrValue = caw.toString ();
                                caw.reset ();
                            } else {
                                caw.write (ch);
                            }
                        }
                        break;
                    case '<':
                        if (stream[seg.start + i + 1] == '%') {
                            i++;
                            StringBuffer sb = new StringBuffer ("<%");
                            while (i < seg.length) {
                                i++;
                                int c = stream[seg.start + i];
                                if (c == '%' && stream[seg.start + i + 1] == '>') {
                                    sb.append ("%>");
                                    i++;
                                    caw.write (sb.toString ());
                                    break;
                                }
                                sb.append ((char) c);
                            }
                        }
                        break;
                    default:
                        caw.write (ch);
                        break;
                }
                if (attrName != null && attrValue != null) {
                    current.setAttribute (attrName.trim (), attrValue);
                    attrName = null;
                    attrValue = null;
                }
            }

            if (current.isEmptyTag ()) current = stack.pop ();
            seg.reset ();
        }
    }

    private void process_comment (Segment seg) {
        if (seg.length > 0) {
            current.addChild (new CommentNode (seg.toString ()));
            skipSpace ();
            seg.reset ();
        }
    }
/*

    private static class DRTester {
        String webuiPrefix = null;
        String dataPrefix = null;
        String menuPrefix = null;
        DocumentReader reader;
        Tag page;

        DRTester () throws IOException, ParseException {
            reader = new DocumentReader (new InputStreamReader (new FileInputStream ("D:/tmp/collection.jasmine"), "UTF-8"));
            Tag root = reader.parse ();
            Tag[] tags = root.getChildren ();

            for (int i = 0; i < tags.length; i++) {
                Tag tag = tags[i];
                if (tag instanceof JspDirective.TaglibDirective) {
                    String uri = ((JspDirective.TaglibDirective) tag).getURI ();
                    if (JasmineTagUtil.WebUI_Component_URI.equals (uri)) webuiPrefix = tag.getPrefix ();
                    else if (JasmineTagUtil.Data_Component_URI.equals (uri)) dataPrefix = tag.getPrefix ();
                    else if (JasmineTagUtil.Menu_Component_URI.equals (uri)) menuPrefix = tag.getPrefix ();
                    continue;
                }

                String prefix = tag.getPrefix ();
                boolean isComponentTag =
                        (webuiPrefix != null && webuiPrefix.equals (prefix)) ||
                                (dataPrefix != null && dataPrefix.equals (prefix)) ||
                                (menuPrefix != null && menuPrefix.equals (prefix));
                if (isComponentTag && !tag.isTextNode ()) {
                    page = tag;
                    break;
                }
            }
            System.out.println (root);
//            test (page);
        }

        void test (Tag parent) {
            Tag[] tags = parent.getChildren ();
            for (int i = 0; i < tags.length; i++) {
                Tag tag = tags[i];
                String prefix = tag.getPrefix ();
                boolean isComponentTag =
                        (webuiPrefix != null && webuiPrefix.equals (prefix)) ||
                                (dataPrefix != null && dataPrefix.equals (prefix)) ||
                                (menuPrefix != null && menuPrefix.equals (prefix));
                if (isComponentTag && !tag.isTextNode ()) {
                    System.out.println (tag);
                }

                if (tag.getName ().equals ("select")) {
                    String[] attrNames = tag.getAttributeNames ();
                    for (int j = 0; j < attrNames.length; j++)
                        System.out.println ("|" + attrNames[j] + "|");
                }

                if (tag.getChildren () != null) test (tag);
            }
        }
    }

    public static void main (String[] args) throws Exception {
        new DRTester ();
    }
*/
}